from handler.HandlerBase import HandlerBase
import time
import threading
import estim2b
import numexpr
from string import Template
import logging
from datetime import datetime


class Estim(HandlerBase):
	def __init__(self, content):
		self._content = content

		for option in self._content['options']:
			self._content['options'][option]['templates'] = {}
			for key in self._content['options'][option]:
				self._content['options'][option]['templates'][key] = Template(self._content['options'][option][key])

		self._lock = threading.Lock()
		self.is_running = True
		self._estim_settings = {
			'a': 0,
			'b': 0,
			'level': 0,
		}

		self._sleep = self._content.get('sleep')
		if self._sleep is None:
			self._sleep = 0.5

		self._data = {
			'seconds': 0,
		}

		self.is_done = False
		self._last_change_a = datetime.utcnow()
		self._last_change_b = datetime.utcnow()
		self._last_change_level = datetime.utcnow()
		self._selected_option = None

		self._estim = estim2b.Estim(content['port'])

		self._runner_thread = threading.Thread(target=self._loop)
		self._runner_thread.start()

	def change_state(self, from_state, to_state):

		if to_state is None:
			return

		option = self._content['options'][to_state]

		self._lock.acquire()

		self._selected_option = option
		if option.get('a') is not None:
			self._last_change_a = datetime.utcnow()
		if option.get('b') is not None:
			self._last_change_b = datetime.utcnow()
		if option.get('level') is not None:
			self._last_change_level = datetime.utcnow()

		self._lock.release()

	def _get_value(self, option, key, min_value, max_value):
		template = option['templates'].get(key)
		if template is None:
			return None

		replaced = template.safe_substitute(self._data)

		value = numexpr.evaluate(replaced).item()

		if value < min_value:
			value = min_value

		if value > max_value:
			value = max_value

		return round(value)

	def _loop(self):
		while self.is_running is True:
			try:
				self._lock.acquire()
				self._update_data()
				self._adjust_stim()
			except Exception as e:
				logging.exception(e)
			finally:
				self._lock.release()
				time.sleep(self._sleep)

		self.is_done = True

	def _update_data(self):
		self._data['seconds_a'] = (datetime.utcnow() - self._last_change_a).total_seconds()
		self._data['seconds_b'] = (datetime.utcnow() - self._last_change_b).total_seconds()
		self._data['seconds_level'] = (datetime.utcnow() - self._last_change_level).total_seconds()

	def _adjust_stim(self):
		if self._selected_option is None:
			return

		a = self._get_value(self._selected_option, 'a', 0, 100)
		b = self._get_value(self._selected_option, 'b', 0, 100)
		level = self._get_value(self._selected_option, 'level', 0, 1)

		a_changed = False
		b_changed = False

		level_changed = False
		if a is not None and a != self._estim_settings['a']:
			self._estim_settings['a'] = a
			a_changed = True

		if b is not None and b != self._estim_settings['b']:
			self._estim_settings['b'] = b
			b_changed = True

		if level is not None and level != self._estim_settings['level']:
			self._estim_settings['level'] = level
			level_changed = True

		if a_changed is True and b_changed is True:
			self._estim.setOutputs(self._estim_settings['a'], self._estim_settings['b'])
		elif a_changed is True:
			self._estim.setOutput('A', self._estim_settings['a'])
		elif b_changed is True:
			self._estim.setOutput('B', self._estim_settings['b'])

		if level_changed is True:
			if self._estim_settings['level'] == 0:
				self._estim.setLow()
			elif self._estim_settings['level'] == 1:
				self._estim.setHigh()

	def close(self):
		self.is_running = False
		while self.is_done is False:
			time.sleep(0.1)

		self._estim.kill()
